package com.zeyu.framework.modules.common.openid.provider;

import com.google.common.collect.Lists;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.message.BasicNameValuePair;
import org.brickred.socialauth.AbstractProvider;
import org.brickred.socialauth.AuthProvider;
import org.brickred.socialauth.Contact;
import org.brickred.socialauth.Permission;
import org.brickred.socialauth.Profile;
import org.brickred.socialauth.exception.SocialAuthConfigurationException;
import org.brickred.socialauth.exception.SocialAuthException;
import org.brickred.socialauth.exception.UserDeniedPermissionException;
import org.brickred.socialauth.oauthstrategy.OAuthStrategyBase;
import org.brickred.socialauth.util.AccessGrant;
import org.brickred.socialauth.util.OAuthConfig;
import org.brickred.socialauth.util.Response;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.InputStream;
import java.io.Serializable;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

/**
 * OSChina第三方登录服务提供者
 * Created by zeyuphoenix on 2017/1/6.
 */
public class OSChinaAuthProvider extends AbstractProvider implements AuthProvider, Serializable {

    // ================================================================
    // Constants
    // ================================================================

    /**
     * uid
     */
    private static final long serialVersionUID = 1L;
    /**
     * logger
     */
    private static final Logger logger = LoggerFactory.getLogger(OSChinaAuthProvider.class);

    // auth url
    private static final String AUTHORIZATION_URL = "https://www.oschina.net/action/oauth2/authorize?client_id=%1$s&redirect_uri=%2$s&response_type=code";
    private static final String ACCESS_TOKEN_URL = "https://www.oschina.net/action/oauth2/token";
    private static final String PROFILE_URL = "https://www.oschina.net/action/oauth2/user?access_token=%1$s";
    private static final String TWEET_URL = "http://www.oschina.net/action/oauth2/tweet";

    // ================================================================
    // Fields
    // ================================================================

    // param
    private String accessToken;
    private String successUrl;
    private boolean isVerify;
    private Permission scope;
    private OAuthConfig config;
    private Profile userProfile;
    private AccessGrant accessGrant;

    // ================================================================
    // Constructors
    // ================================================================

    /**
     * Stores configuration for the provider
     *
     * @param providerConfig
     *            It contains the configuration of application like consumer key
     *            and consumer secret
     * @throws Exception
     */
    public OSChinaAuthProvider(final OAuthConfig providerConfig)
            throws Exception {
        config = providerConfig;
        if (config.getCustomPermissions() != null) {
            this.scope = Permission.CUSTOM;
        }
    }

    // ================================================================
    // Methods from/for super Interfaces or SuperClass
    // ================================================================

    /**
     * Stores access grant for the provider
     *
     * @param accessGrant
     *            It contains the access token and other information
     */
    @Override
    public void setAccessGrant(final AccessGrant accessGrant) {
        this.accessGrant = accessGrant;
        accessToken = accessGrant.getKey();
        isVerify = true;
    }

    /**
     * This is the most important action. It redirects the browser to an
     * appropriate URL which will be used for authentication with the provider
     * that has been set using setId()
     *
     * @throws Exception
     */

    @Override
    public String getLoginRedirectURL(final String successUrl) throws Exception {
        logger.info("Determining URL for redirection");
        this.successUrl = successUrl;
        String url = String.format(AUTHORIZATION_URL, config.get_consumerKey(),
                this.successUrl);
        String scopeStr = getScope();
        if (scopeStr != null) {
            url += "&scope=" + scopeStr;
        }
        logger.info("Redirection to following URL should happen : " + url);
        return url;
    }

    /**
     * Verifies the user when the external provider redirects back to our
     * application.
     *
     *
     * @param requestParams
     *            request parameters, received from the provider
     * @return Profile object containing the profile information
     * @throws Exception
     */
    @Override
    public Profile verifyResponse(final Map<String, String> requestParams)
            throws Exception {
        return doVerifyResponse(requestParams);
    }

    /**
     * Gets the list of contacts of the user and their email.
     *
     * @return List of profile objects representing Contacts. Only name and
     *         email will be available
     * @throws Exception
     */

    @Override
    public List<Contact> getContactList() throws Exception {
        logger.warn("WARNING: Not implemented for OsChina");
        throw new SocialAuthException(
                "Get contact list is not implemented for OsChina");
    }

    /**
     * Updates the status on the chosen provider if available. This may not be
     * implemented for all providers.
     *
     * @param msg
     *            Message to be shown as user's status
     * @throws Exception
     */
    @Override
    public Response updateStatus(final String msg) throws Exception {
        CloseableHttpClient hc = HttpClientBuilder.create()
                .setUserAgent("Mozilla/5.0 (X11; U; Linux i686; zh-CN; rv:1.9.1.2) Gecko/20090803").build();
        try {
            HttpPost post = new HttpPost(TWEET_URL);

            List<NameValuePair> formparams = Lists.newArrayList();
            formparams.add(new BasicNameValuePair("access_token", this.accessGrant
                    .getKey()));
            formparams.add(new BasicNameValuePair("msg", msg));
            formparams.add(new BasicNameValuePair("magic", "OSCHINA"));
            post.setEntity(new UrlEncodedFormEntity(formparams, "UTF-8"));
            hc.execute(post);
        } catch (Exception e) {
            logger.error("update status error: ", e);
        } finally {
            try {
                hc.close();
            } catch (Exception ignored) {
            }
        }
        return null;
    }

    /**
     * Logout
     */
    @Override
    public void logout() {
        accessToken = null;
        accessGrant = null;
    }

    /**
     *
     * @param p
     *            Permission object which can be Permission.AUHTHENTICATE_ONLY,
     *            Permission.ALL, Permission.DEFAULT
     */
    @Override
    public void setPermission(final Permission p) {
        this.scope = p;
    }

    /**
     * Makes HTTP request to a given URL.
     *
     * @param url
     *            URL to make HTTP request.
     * @param methodType
     *            Method type can be GET, POST or PUT
     * @param params
     *            Any additional parameters whose signature need to compute.
     *            Only used in case of "POST" and "PUT" method type.
     * @param headerParams
     *            Any additional parameters need to pass as Header Parameters
     * @param body
     *            Request Body
     * @return Response object
     * @throws Exception
     */
    @Override
    public Response api(final String url, final String methodType,
                        final Map<String, String> params,
                        final Map<String, String> headerParams, final String body)
            throws Exception {
        return null;
    }

    /**
     * Retrieves the user profile.
     *
     * @return Profile object containing the profile information.
     */
    @Override
    public Profile getUserProfile() throws Exception {
        if (userProfile == null && accessGrant != null) {
            getProfile();
        }
        return userProfile;
    }

    @Override
    public AccessGrant getAccessGrant() {
        return accessGrant;
    }

    @Override
    public String getProviderId() {
        return config.getId();
    }

    @Override
    public Response uploadImage(final String message, final String fileName,
                                final InputStream inputStream) throws Exception {
        logger.warn("WARNING: Not implemented for OsChina");
        throw new SocialAuthException(
                "Upload Image is not implemented for OsChina");
    }

    @Override
    protected List<String> getPluginsList() {
        List<String> list = Lists.newArrayList();
        if (config.getRegisteredPlugins() != null
                && config.getRegisteredPlugins().length > 0) {
            list.addAll(Arrays.asList(config.getRegisteredPlugins()));
        }
        return list;
    }

    @Override
    protected OAuthStrategyBase getOauthStrategy() {
        return null;
    }

    // ================================================================
    // Public or Protected Methods
    // ================================================================

    // ================================================================
    // Getter & Setter
    // ================================================================

    // ================================================================
    // Private Methods
    // ================================================================


    private Profile doVerifyResponse(final Map<String, String> requestParams)
            throws Exception {
        logger.info("Retrieving Access Token in verify response function");
        if (requestParams.get("error") != null
                && "access_denied".equals(requestParams.get("error"))) {
            throw new UserDeniedPermissionException();
        }
        String code = requestParams.get("code");
        if (code == null || code.length() == 0) {
            throw new SocialAuthException("Verification code is null");
        }
        String result = null;
        CloseableHttpClient hc = HttpClientBuilder.create()
                .setUserAgent("Mozilla/5.0 (X11; U; Linux i686; zh-CN; rv:1.9.1.2) Gecko/20090803").build();
        HttpPost post = new HttpPost(ACCESS_TOKEN_URL);

        List<NameValuePair> formparams = Lists.newArrayList();

        formparams.add(new BasicNameValuePair("client_id", config
                .get_consumerKey()));
        formparams.add(new BasicNameValuePair("client_secret", config
                .get_consumerSecret()));
        formparams.add(new BasicNameValuePair("grant_type",
                "authorization_code"));
        formparams.add(new BasicNameValuePair("redirect_uri", this.successUrl));
        formparams.add(new BasicNameValuePair("code", code));
        formparams.add(new BasicNameValuePair("code", code));
        post.setEntity(new UrlEncodedFormEntity(formparams, "UTF-8"));

        try {
            HttpResponse resp = hc.execute(post);
            HttpEntity entity = resp.getEntity();
            result = IOUtils.toString(entity.getContent(), "UTF-8");
        } catch (Exception e) {
            throw new SocialAuthException("Error in url : " + ACCESS_TOKEN_URL
                    + e);
        } finally {
            try {
                hc.close();
            } catch (Exception ignored) {
            }
        }

        if (StringUtils.isBlank(result)) {
            throw new SocialAuthConfigurationException(
                    "Problem in getting Access Token. Application key or Secret key may be wrong."
                            + "The server running the application should be same that was registered to get the keys.");
        }

        JSONObject resp = new JSONObject(result);
        accessToken = resp.getString("access_token");
        logger.debug("Access Token : " + accessToken);

        if (accessToken != null) {
            isVerify = true;
            accessGrant = new AccessGrant();
            accessGrant.setKey(accessToken);
            if (scope != null) {
                accessGrant.setPermission(scope);
            } else {
                accessGrant.setPermission(Permission.ALL);
            }
            accessGrant.setProviderId(getProviderId());

            return getProfile();
        } else {
            throw new SocialAuthException(
                    "Access token and expires not found from "
                            + ACCESS_TOKEN_URL);
        }

    }

    private Profile getProfile() throws Exception {
        if (!isVerify || accessToken == null) {
            throw new SocialAuthException(
                    "Please call verifyResponse function first to get Access Token and then update status");
        }

        Profile p = new Profile();
        String result;
        CloseableHttpClient hc = HttpClientBuilder.create()
                .setUserAgent("Mozilla/5.0 (X11; U; Linux i686; zh-CN; rv:1.9.1.2) Gecko/20090803").build();
        try {
            HttpGet get = new HttpGet(String.format(PROFILE_URL, accessToken));
            HttpResponse resp = hc.execute(get);
            HttpEntity entity = resp.getEntity();
            result = IOUtils.toString(entity.getContent(), "UTF-8");
        } catch (Exception e) {
            throw new SocialAuthException("Error in url : " + ACCESS_TOKEN_URL
                    + e);
        } finally {
            try {
                hc.close();
            } catch (Exception ignored) {
            }
        }

        if (StringUtils.isBlank(result)) {
            throw new SocialAuthException(
                    "Problem in getting user profile. Access Token maybe invalid.");
        }

        try {
            JSONObject resp = new JSONObject(result);
            if (resp.has("id")) {
                p.setValidatedId(resp.get("id").toString());
            }
            if (resp.has("name")) {
                p.setFullName(resp.getString("name"));
            }
            if (resp.has("url")) {
                p.setLocation(resp.getString("url"));
            }
            if (resp.has("gender")) {
                p.setGender(resp.getString("gender"));
            }
            if (resp.has("avatar")) {
                p.setProfileImageURL(resp.getString("avatar"));
            }
            if (resp.has("email")) {
                p.setEmail(resp.getString("email"));
            }
            p.setProviderId(getProviderId());
            userProfile = p;
            return p;
        } catch (Exception e) {
            throw new SocialAuthException(
                    "Failed to parse the user profile json : " + result, e);
        }
    }

    private String getScope() {
        String scopeStr = null;
        if (Permission.CUSTOM.equals(scope)) {
            scopeStr = config.getCustomPermissions();
        }
        return scopeStr;
    }

    // ================================================================
    // Inner or Anonymous Class
    // ================================================================

    // ================================================================
    // Test Methods
    // ================================================================

}
